﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using PlaytimeSaveTypes;

namespace BoneAnimationProt
{
    public class JointAnimation
    {
        public List<Joint> joints;
        public Dictionary<float, List<JointData>> keyframes;

        public float animationLength;
        public float animationTimer = 0;
        public float animationSpeed = 1f;

        public bool IsPlaying = false;

        public JointAnimation(List<Joint> _joints, Dictionary<float, List<JointData>> _keyframes, float _animationLength)
        {
            joints = _joints;
            keyframes = _keyframes;
            animationLength = _animationLength;
        }

        public JointAnimation(JointAnimationSaveData _data)
        {
            joints = new List<Joint>();
            keyframes = new Dictionary<float, List<JointData>>();
            animationLength = _data.animationLength;
            animationSpeed = _data.animationSpeed;

            foreach (JointSaveData joint in _data.joints)
            {
                AddJoint(new Joint(joint));
            }

            foreach (KeyValuePair<float, List<JointData>> keyframe in _data.keyframes)
            {
                AddKeyFrame(keyframe.Value, keyframe.Key);
            }

            for (int i = 0; i < _data.joints.Count; i++)
            {
                Joint parent = null;
                foreach (Joint joint in joints)
                {
                    if (joint.Name == _data.joints[i].ParentName)
                    {
                        parent = joint;
                        break;
                    }
                }
                joints[i].SetParent(parent);
            }

        }

        public JointAnimation(float _animationLength)
        {
            joints = new List<Joint>();
            keyframes = new Dictionary<float, List<JointData>>();
            animationLength = _animationLength;
        }

        public JointAnimationSaveData ToSaveData()
        {
            return TypeBuilder.CreateAnimationSaveData(this);
        }

        public Joint this[string _name]
        {
            get { foreach (Joint joint in joints) if (joint.Name == _name) return joint; return null; }
        }

        public void AddJoint(Joint _joint)
        {
            joints.Add(_joint);
            foreach (List<JointData> frameJoints in keyframes.Values)
                frameJoints.Add(new JointData(_joint.Name, _joint.relativeRotation, _joint.relativeTranslation));
        }

        public void AddKeyFrame(List<Joint> _changedJoints, float _key)
        {
            List<JointData> newKeyframeData = new List<JointData>();
            foreach (Joint joint in joints)
            {
                JointData newJointData = new JointData(joint.Name, joint.relativeRotation, joint.relativeTranslation);
                for (int i = 0; i < _changedJoints.Count; i++)
                {
                    if (joint.Name == _changedJoints[i].Name)
                    {
                        newJointData.relativeRotation = _changedJoints[i].relativeRotation;
                        newJointData.relativeTranslation = _changedJoints[i].relativeTranslation;
                        break;
                    }
                }
                newKeyframeData.Add(newJointData);
            }
            keyframes[_key] = newKeyframeData;
        }

        public void AddKeyFrame(List<JointData> _changedJoints, float _key)
        {
            List<JointData> newKeyframeData = new List<JointData>();
            foreach (Joint joint in joints)
            {
                JointData newJointData = new JointData(joint.Name, joint.relativeRotation, joint.relativeTranslation);
                for (int i = 0; i < _changedJoints.Count; i++)
                {
                    if (joint.Name == _changedJoints[i].Name)
                    {
                        newJointData.relativeRotation = _changedJoints[i].relativeRotation;
                        newJointData.relativeTranslation = _changedJoints[i].relativeTranslation;
                        break;
                    }
                }
                newKeyframeData.Add(newJointData);
            }
            keyframes[_key] = newKeyframeData;
        }

        public void DeleteKeyFrame(float _key)
        {
            if (keyframes.ContainsKey(_key))
                keyframes.Remove(_key);
            JumpTo(animationTimer);
        }

        public void Play()
        {
            IsPlaying = true;
        }

        public void Pause()
        {
            IsPlaying = false;
        }

        public void Stop()
        {
            JumpTo(0);
        }

        public void JumpTo(float _animTime)
        {
            if (_animTime >= 0 && _animTime <= animationLength)
            {
                IsPlaying = true;
                animationTimer = _animTime;
                Update(new GameTime());
                IsPlaying = false;
            }
        }

        public void NextKeyframe()
        {
            float nearestKey = 0;
            float smallestDist = float.PositiveInfinity;
            bool keyFound = false;
            foreach (float key in keyframes.Keys)
            {
                float dist = key - animationTimer;
                if (dist > 0 && dist < smallestDist)
                {
                    smallestDist = dist;
                    nearestKey = key;
                    keyFound = true;
                }
            }

            if (keyFound)
            {
                JumpTo(nearestKey);
            }
        }

        public void PreviousKeyframe()
        {
            float nearestKey = 0;
            float smallestDist = float.PositiveInfinity;
            bool keyFound = false;
            foreach (float key in keyframes.Keys)
            {
                float dist = animationTimer - key;
                if (dist > 0 && dist < smallestDist)
                {
                    smallestDist = dist;
                    nearestKey = key;
                    keyFound = true;
                }
            }

            if (keyFound)
            {
                JumpTo(nearestKey);
            }
        }

        public void Update(GameTime _gameTime)
        {
            if (!IsPlaying)
                return;

            animationTimer += (float)_gameTime.ElapsedGameTime.TotalSeconds * animationSpeed;

            if (animationTimer > animationLength)
                animationTimer = 0;

            if (keyframes.Count == 0)
                return;

            List<float> keyValues = new List<float>(keyframes.Keys);
            keyValues.Sort();

            int min;
            int max;
            BinarySearchNearest(keyValues, animationTimer, out min, out max);

            min %= keyValues.Count;
            max %= keyValues.Count;
            List<JointData> frame1 = keyframes[keyValues[min]];
            List<JointData> frame2 = keyframes[keyValues[max]];
            if (min != max)
            {
                float percent = MathHelper.Clamp((animationTimer - keyValues[min]) / (keyValues[max] - keyValues[min]), 0, 1);

                for (int i = 0; i < joints.Count; i++)
                {
                    joints[i].relativeRotation = MathHelper.Lerp(frame1[i].relativeRotation, frame2[i].relativeRotation, percent);
                    joints[i].relativeTranslation = Vector2.Lerp(frame1[i].relativeTranslation, frame2[i].relativeTranslation, percent);
                }
            }
            else
            {
                for (int i = 0; i < joints.Count; i++)
                {
                    joints[i].relativeRotation = frame1[i].relativeRotation;
                    joints[i].relativeTranslation = frame1[i].relativeTranslation;
                }
            }
        }

        public static void BinarySearchNearest(List<float> _list, float _key, out int min, out int max)
        {
            min = 0;
            float smallestDist = float.PositiveInfinity;
            for (int i = 0; i < _list.Count; i++)
            {
                float dist = _key - _list[i];
                if (dist >= 0 && dist < smallestDist)
                {
                    smallestDist = dist;
                    min = i;
                }
            }

            max = 0;
            smallestDist = float.PositiveInfinity;
            for (int i = 0; i < _list.Count; i++)
            {
                float dist = _list[i] - _key;
                if (dist >= 0 && dist < smallestDist)
                {
                    smallestDist = dist;
                    max = i;
                }
            }
        }
    }
}
